package com.company.exp2.context;

import com.company.exp2.Interface_.ChannelListener;
import com.company.exp2.factory.TimeLineFactory;
import com.company.exp2.frame.Frame;
import com.company.exp2.station.Station;
import com.company.exp2.timeLine.TimeLine;
import com.company.exp2.typeEnum.TimeLineType;

/**
 * @author dyy
 *
 * 系统运行环境，用来实现通知功能，
 *
 */
public abstract class Context implements ChannelListener {

    public static Context currentContext;

    protected final TimeLine timeLine;          //  当前环境下的时间线
    protected Frame currentTransmittingFrame;   //  当前正在传输的帧
    protected boolean hasCollided;              //  是否碰撞

    public Context() {
        this.timeLine = TimeLineFactory.createDefaultInstance();
        currentContext = this;
        hasCollided = false;
    }

    public Context(TimeLineType type) {
        this.timeLine = TimeLineFactory.createInstance(type);
        currentContext = this;
    }

    /**
     * 返回当前信道中是否空闲
     * 如果将要发送数据前发现信道并不空闲，说明即将发生碰撞，应该处理发生碰撞的场合
     */
    public abstract boolean isVacant();

    /**
     * 向上下文中注册站点
     * @param station 需要注册的站点
     */
    protected abstract void registerStation(Station station);

    public void onTransmissionComplete() {
        this.currentTransmittingFrame = null;
        this.hasCollided = false;
    }

    /**
     * 返回站点个数
     * @return 站点的个数
     */
    public abstract int getStationSize();

    /**
     * 设置当前的传输帧
     * @param currentTransmittingFrame  当前传输帧
     */
    public void setCurrentTransmittingFrame(Frame currentTransmittingFrame) {
        this.currentTransmittingFrame = currentTransmittingFrame;
    }

    /**
     * 获取当前传输帧
     */
    public Frame getCurrentTransmittingFrame() {
        return currentTransmittingFrame;
    }

    /**
     * 获取上下文中的时间线实例
     * @return TimeLine 时间线
     */
    public TimeLine getTimeLine() {
        return timeLine;
    }

    /**
     * 当一个新的帧开始发送的下一个时间点调用这一函数
     * 由于这个事件总是比其他站发送事件声明得晚，故可以允许帧碰撞
     * 这就要求对于多个相同时间点的事件，谁先声明谁先触发
     * 通知所有站点的 onChannelForbidden
     *
     */
    @Override
    public abstract void onChannelForbidden(long timestamp);

    /**
     * 当上一个帧发送结束且度过一段事件后调用
     *
     * 注意，此时在这里应当将上一次传输的帧置为 null，以保证系统健壮
     *
     * 通知所有注册的站点。触发他们的 onChannelTransmittable
     */
    @Override
    public abstract void onChannelTransmittable(long timestamp);

    /**
     * Context运行逻辑
     */
    public void start() {
        init();
        assert timeLine != null : "启动上下文时时间线不能为null";
        assert getStationSize() != 0 : "启动上下文时站点个数不能为零";
        timeLine.start();
        logAllStationResult();
    }

    /**
     * 输出所有的 Station 的日志信息
     * 这些信息描述了整个模拟过程中 station 的队列长度分布和等待时间分布
     */
    protected abstract void logAllStationResult();

    /**
     * 初始化上下文逻辑
     * 注册五个站点
     * 声明所有站点的 CREATE_FRAME 事件
     * 声明所有站点的最初的 CHANNEL_TRANSMITTABLE 事件
     */
    protected abstract void init();

    public boolean hasNotCollided() {
        return !hasCollided;
    }

    public void setHasCollided(boolean hasCollided) {
        this.hasCollided = hasCollided;
    }

}
